#pragma once

#include <QtMath>
#include <QSize>
#include <QString>
#include <QRunnable>
#include <QByteArray>
#include <QPointF>
#include <QImage>
#include <QObject>
#include <QSqlDatabase>

#include <layerprovider.h>
#include <pointxy.h>

namespace EasyGIS
{
  /**
   * 瓦片的信息描述，用于在线程之间传递瓦片数据
   */
  struct TileInfo
  {
    QPointF index;
    QPoint position;
    int zoom;
    QPoint coord;
    QString url;
    QByteArray data;
  };

  /**
   * 异步执行的瓦片下载任务
   */
  class TileDownloadTask : public QObject, public QRunnable
  {
  Q_OBJECT

  signals:
    void tileReady(TileInfo tile);

  public:
    explicit TileDownloadTask(TileInfo tile, QObject* parent = nullptr);
    TileDownloadTask(const TileDownloadTask& other) = delete;
    TileDownloadTask(TileDownloadTask&& other) = delete;
    TileDownloadTask& operator=(const TileDownloadTask& other) = delete;
    TileDownloadTask& operator=(TileDownloadTask&& other) = delete;

    ~TileDownloadTask() override = default;

  public:
    void run() override;

  protected:
    TileInfo mTile;
  };

  class TmsProvider : public LayerProvider
  {
  Q_OBJECT

  public:
    explicit TmsProvider(QObject* parent = nullptr);
    ~TmsProvider() override;

  public:
    /**
     * 获取瓦片的像素大小
     * @return 瓦片大小，一般为256*256
     */
    virtual const QSize tileSize() const = 0;

    /**
     * 获取瓦片的url
     * @param pos 瓦片的坐标
     * @param zoom zoom值
     * @return 瓦片的url，失败时则为空字符串
     */
    virtual QString tileUrl(const PointXY& pos, int zoom) const = 0;

    /**
     * 返回可显示的图像内容
     * @return 图像对象
     */
    const QImage preparedImage() const override { return *mImage; }

    /**
     * 判断是否有可显示图像内容
     * @return 有则为true，否则false
     */
    bool hasContent() const override { return static_cast<bool>(mImage); }

    /**
     * 获取tms提供者的名称
     * @return
     */
    virtual const QString& id() const = 0;

  public slots:
    /**
     * 创建下载任务
     * @param rect 下载区域
     * @param zoom zoom值
     */
    void createTask(const QRectF& rect, int zoom) override;

    /**
     * 瓦片下载成功时处理函数
     * @param tile 瓦片对象
     */
    void tileReady(TileInfo tile);

  protected:
    /**
     * 创建一个新的绘制对象
     * @param rect
     */
    void newImage(const QRectF& rect);

    /**
     * 从文件缓存获取瓦片
     * @param pos 瓦片位置
     * @param zoom 瓦片zoom值
     * @return 瓦片数据
     */
    virtual QByteArray getCache(const QPoint& pos, int zoom);

    /**
     * 添加瓦片进文件缓存
     * @param pos 瓦片位置
     * @param zoom 瓦片zoom值
     * @return 是否成功
     */
    virtual bool addCache(const QPoint& pos, int zoom, QByteArray data);

    /**
     * 判断缓存是否存在指定的瓦片数据
     * @param pos 瓦片位置
     * @param zoom zoom值
     * @return 是否存在，true则存在，否则false
     */
    virtual bool cacheContains(const QPoint& pos, int zoom);

    /**
     * 初始化文件缓存数据库
     */
    virtual bool initCache();

  protected:
    /**
     * 瓦片渲染对象，每次新建任务时都会重新构建该对象
     */
    QImage* mImage;

    /**
     * 缓存数据库名称
     */
    const QString mDbName{"easygis.sqlite"};

    /**
     * 缓存表名称
     */
    const QString mTableName{"tiles"};

    /**
     * 缓存数据库的连接
     */
    QSqlDatabase mDbConn;
  };
}

inline uint qHash(const QPoint& p)
{
  return qHash(qPow(p.x(), p.y()));
}
